package org.apache.shardingsphere.shardingjdbc.jdbc.adapter;

import com.google.common.base.Preconditions;
import lombok.Getter;
import lombok.Setter;
import org.apache.shardingsphere.core.constant.DatabaseType;
import org.apache.shardingsphere.shardingjdbc.jdbc.unsupported.AbstractUnsupportedOperationDataSource;
import org.apache.shardingsphere.transaction.ShardingTransactionManagerEngine;

import javax.sql.DataSource;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.*;
import java.util.logging.Logger;

/**
 * 抽象数据源适配器
 * Adapter for {@code Datasource}.
 *
 * @author zhangliang
 * @author panjuan
 * @author zhaojun
 * @author liangyehao
 * @date 2021/09/24
 */
@Getter
@Setter
public abstract class AbstractDataSourceAdapter extends AbstractUnsupportedOperationDataSource implements AutoCloseable {

    private final DatabaseType databaseType;

    private final Map<String, DataSource> dataSourceMap;

    private ShardingTransactionManagerEngine shardingTransactionManagerEngine = new ShardingTransactionManagerEngine();

    private PrintWriter logWriter = new PrintWriter(System.out);

    public AbstractDataSourceAdapter(final Map<String, DataSource> dataSourceMap) throws SQLException {
        databaseType = getDatabaseType(dataSourceMap.values());
        shardingTransactionManagerEngine.init(databaseType, dataSourceMap);
        this.dataSourceMap = dataSourceMap;
    }

    protected final DatabaseType getDatabaseType(final Collection<DataSource> dataSources) throws SQLException {
        Set<DatabaseType> databaseTypeSet = new HashSet<>();
        for (DataSource each : dataSources) {
            DatabaseType databaseType = getDatabaseType(each);
            databaseTypeSet.add(databaseType);
        }
        if (databaseTypeSet.size()==1) {
            for (DatabaseType type : databaseTypeSet) {
                return type;
            }
        }
        Preconditions.checkState(databaseTypeSet.size()<=2 && isXc(databaseTypeSet), String.format("Database type inconsistent with %s ", databaseTypeSet));
        return DatabaseType.Oracle;
    }

    /**
     * xc 允许支持kingbaseES和oracle
     *
     * @return boolean
     * @param databaseTypeSet 数据源类型集合
     */
    private boolean isXc(Set<DatabaseType> databaseTypeSet) {
        for (DatabaseType type : databaseTypeSet) {
            if (!(type.equals(DatabaseType.KingbaseES) || type.equals(DatabaseType.Oracle))) {
                return false;
            }
        }
        return true;
    }

    private DatabaseType getDatabaseType(final DataSource dataSource) throws SQLException {
        if (dataSource instanceof AbstractDataSourceAdapter) {
            return ((AbstractDataSourceAdapter) dataSource).databaseType;
        }
        try (Connection connection = dataSource.getConnection()) {
            return DatabaseType.valueFrom(connection.getMetaData().getDatabaseProductName());
        }
    }

    @Override
    public final Logger getParentLogger() {
        return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
    }

    @Override
    public final Connection getConnection(final String username, final String password) throws SQLException {
        return getConnection();
    }

    @Override
    public void close() throws Exception {
        for (DataSource each : dataSourceMap.values()) {
            try {
                Method method = each.getClass().getDeclaredMethod("close");
                method.setAccessible(true);
                method.invoke(each);
            } catch (final ReflectiveOperationException ignored) {
            }
        }
        shardingTransactionManagerEngine.close();
    }
}
